﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Syndication;
using System.ServiceModel.Web;
using NCMIS.AccessControl;
using NCMIS.ObjectModel;
using NCMIS.ObjectModel.Rest;
using NCMIS.Produce;
using NCMIS.Provider;
using NCMIS.ServiceModel.Syndication;

namespace NCMIS.ServiceModel.Ra
{
    /// <summary>
    /// Provides the Versioning Services for the REST/Atom binding. The versioning services are used to navigate or update a Document Version Series.
    /// </summary>
    public partial class Service : IService
    {
        /// <summary>
        /// Creates a Private Working Copy (PWC) of the specified document.
        /// </summary>
        /// <param name="repositoryId">The id of the repository.</param>
        /// <param name="documentId">The id of the document.</param>
        /// <returns>The id of the PWC document, formatted as an Atom 1.0 entry.</returns>
        [WebInvoke(Method = "POST", UriTemplate = "/{repositoryId}/checkedout/{objectId}")]
        public Atom10ItemFormatter CheckOut(string repositoryId, string objectId)
        {
            OutgoingWebResponseContext ctx = WebOperationContext.Current.OutgoingResponse;
            ctx.ContentType = CmisContentType.Feed;

            // Repository ID and object ID must be specified
            if (String.IsNullOrEmpty(repositoryId) || String.IsNullOrEmpty(objectId))
            {
                ctx.StatusCode = ServiceException.InvalidArgument;
                ctx.StatusDescription = String.Format("{0}: The parameters repositoryId and documentId must not be null or empty.",
                    ServiceException.InvalidArgument.ToString());
                return null;
            }

            // Create the PWC copy 
            CopyFlaggedDocumentId copyFlaggedDocumentId = VersioningProviderManager.Provider.CheckOut(repositoryId, objectId);

            // Store the PWC as an syndication entry
            CmisSyndicationItem entry = new CmisSyndicationItem()
            {
                BaseUri = _baseUri,
                Id = "urn:uuid:document",
                ServiceLinkUri = new Uri(_baseUri, String.Format("{0}/servicedoc", repositoryId)),
                Title = new TextSyndicationContent("Checked Out Document"),
            };
            entry.AddDocumentId(copyFlaggedDocumentId);

            // Format the entry as an Atom entry
            return new Atom10ItemFormatter(entry);
        }

        /// <summary>
        /// Reverses the effect of a check-out. Removes the private working copy of the checked-out document, allowing other documents in the
        /// version series to be checked out again.
        /// </summary>
        /// <param name="repositoryId">The id of the repository.</param>
        /// <param name="documentId">the id of the document.</param>
        [WebInvoke(Method = "DELETE", UriTemplate = "/{repositoryId}/object/{objectId}/pwc")]
        public void CancelCheckOut(string repositoryId, string objectId)
        {
            OutgoingWebResponseContext ctx = WebOperationContext.Current.OutgoingResponse;
            ctx.ContentType = CmisContentType.Feed;

            // Repository ID and object ID must be specified
            if (String.IsNullOrEmpty(repositoryId) || String.IsNullOrEmpty(objectId))
            {
                ctx.StatusCode = ServiceException.InvalidArgument;
                ctx.StatusDescription = String.Format("{0}: The parameters repositoryId and documentId must not be null or empty.",
                    ServiceException.InvalidArgument.ToString());
                return;
            }

            // Remove the PWC
            VersioningProviderManager.Provider.CancelCheckOut(repositoryId, objectId);
        }

        /// <summary>
        /// Checks-in the Private Working Copy document.
        /// </summary>
        /// <param name="repositoryId">The id of the repository.</param>
        /// <param name="documentId">The id of the document.</param>
        /// <param name="major">True if the document must be a major version, false if the document must not be a major version. (Optional, default: false)</param>
        /// <param name="properties">The property values that must be applied to the checked-in document.</param>
        /// <param name="contentStream">The content stream that must be stored for the checked-in document.</param>
        /// <param name="checkinComment"></param>
        /// <param name="policies">A list of policy ids that must be applied to the newly-created document.</param>
        /// <param name="addACEs">A list of ACEs that must be applied to the newly-created document, either using ACL from folderId if specified, or being applied if no folder is specified.</param>
        /// <param name="removeACEs">a list of ACEs that must be removed from the newly-created document, either using ACL from folderId if specified, or being applied if no folder is specified.</param>
        /// <returns>The id of the checked-in document.</returns>
        [WebInvoke(Method = "PUT", UriTemplate = "/{repositoryId}/object/{objectId}/pwc")]
        public string CheckIn(string repositoryId, string objectId, Stream stream)
        {
            bool major = RequestParameter.MajorTrue;
            string checkinComment = RequestParameter.CheckinComment;

            // TODO:
            // CmisProperties properties, ContentStream contentStream, string[] policies, AccessControlList addACEs,
            // AccessControlList removeACEs --> stream

            OutgoingWebResponseContext ctx = WebOperationContext.Current.OutgoingResponse;
            ctx.ContentType = CmisContentType.Feed;

            // Repository ID and object ID must be specified
            if (String.IsNullOrEmpty(repositoryId) || String.IsNullOrEmpty(objectId))
            {
                ctx.StatusCode = ServiceException.InvalidArgument;
                ctx.StatusDescription = String.Format("{0}: The parameters repositoryId and documentId must not be null or empty.",
                    ServiceException.InvalidArgument.ToString());
                return null;
            }

            // Check in the PWC
            return VersioningProviderManager.Provider.CheckIn(repositoryId, objectId, major, null, null, checkinComment, null, null, null);
        }

        /// <summary>
        ///  Gets the latest Document object in the Version Series.
        /// </summary>
        /// <param name="repositoryId">The id of the repository.</param>
        /// <param name="objectId">The id of the object.</param>
        /// <param name="major">True if the document must be a major version, false if the document must not be a major version. (Optional, default: false)</param>
        /// <param name="filter">Value indicating which properties for Objects that must be returned. (Optional, default: Not set)</param>
        /// <param name="includeAllowableActions">If true, then the the available actions for each object in the result set must be returned. (Optional, default: false)</param>
        /// <param name="includeRelationships">Value indicating what relationships in which the objects returned participate that must be returned, if any.</param>
        /// <param name="renditionFilter">Renditions whose kind matches this filter must be returned. (Optional, default: cmis:none)</param>
        /// <param name="includePolicyIds">If true, then the Repository must include the IDs of Policies applied to the object referenced in each change event, if the change
        /// event modified the set of policies applied to the object. (Optional, default: false)</param>
        /// <param name="includeAcl">If true, then the repository must return the ACLs for each object in the result set. (Optional, default: false)</param>
        /// <returns>Document object, formatted as Atom 1.0 entry.</returns>
        [WebGet(UriTemplate = "/{repositoryId}/object/{objectId}/currentversion")]
        public Atom10ItemFormatter GetObjectOfLatestVersion(string repositoryId, string objectId)
        {
            string folderId = RequestParameter.FolderId;
            bool major = RequestParameter.Major;
            string filter = RequestParameter.Filter;
            bool includeAllowableActions = RequestParameter.IncludeAllowableActions;
            IncludeRelationships includeRelationships = RequestParameter.IncludeRelationships;
            string renditionFilter = RequestParameter.RenditionFilter;
            bool includePolicyIds = RequestParameter.IncludePolicyIds;
            bool includeAcl = RequestParameter.IncludeAcl;

            OutgoingWebResponseContext ctx = WebOperationContext.Current.OutgoingResponse;
            ctx.ContentType = CmisContentType.Entry;

            // Repository ID and object ID must be specified
            if (String.IsNullOrEmpty(repositoryId) || String.IsNullOrEmpty(objectId))
            {
                ctx.StatusCode = ServiceException.InvalidArgument;
                ctx.StatusDescription = String.Format("{0}: The parameters repositoryId and objectId must not be null or empty.",
                    ServiceException.InvalidArgument.ToString());
                return null;
            }

            // Get the latest version of the specified CMIS object
            CmisObject cmisObject = VersioningProviderManager.Provider.GetObjectOfLatestVersion(repositoryId, objectId, major, filter, includeAllowableActions, includeRelationships,
                renditionFilter, includePolicyIds, includeAcl);

            // Create an syndication item to hold the CMIS object
            CmisSyndicationItem entry = new CmisSyndicationItem()
            {
                BaseUri = _baseUri,
                Id = "urn:uuid:base-types",
                ServiceLinkUri = new Uri(_baseUri, String.Format("{0}/servicedoc", repositoryId)),
                Title = new TextSyndicationContent("Base Types"),
            };
            entry.AddObject(cmisObject);

            // Format the syndication item as an Atom entry
            return new Atom10ItemFormatter(entry);
        }

        /// <summary>
        /// Gets a subset of the properties for the latest Document Object in the Version Series.
        /// </summary>
        /// <param name="repositoryId">The id of the repository.</param>
        /// <param name="objectId">The id of the object.</param>
        /// <param name="major">True if the document must be a major version, false if the document must not be a major version. (Optional, default: false)</param>
        /// <param name="filter">Value indicating which properties for Objects that must be returned. (Optional, default: Not set)</param>
        /// <returns>CMIS propertires, formatted as Atom 1.0 entry.</returns>
        [WebGet(UriTemplate = "/{repositoryId}/document/properties/{objectId}")]
        public Atom10ItemFormatter GetPropertiesOfLatestVersion(string repositoryId, string objectId)
        {
            bool major = RequestParameter.Major;
            string filter = RequestParameter.Filter;

            OutgoingWebResponseContext ctx = WebOperationContext.Current.OutgoingResponse;
            ctx.ContentType = CmisContentType.Entry;

            // Repository ID and object ID must be specified
            if (String.IsNullOrEmpty(repositoryId) || String.IsNullOrEmpty(objectId))
            {
                ctx.StatusCode = ServiceException.InvalidArgument;
                ctx.StatusDescription = String.Format("{0}: The parameters repositoryId and objectId must not be null or empty.",
                    ServiceException.InvalidArgument.ToString());
                return null;
            }

            // Get the properties and store them in a syndication item
            CmisProperties properties = VersioningProviderManager.Provider.GetPropertiesOfLatestVersion(repositoryId, objectId, major, filter);

            CmisSyndicationItem entry = new CmisSyndicationItem()
            {
                BaseUri = _baseUri,
                Id = "urn:uuid:base-types",
                ServiceLinkUri = new Uri(_baseUri, String.Format("{0}/servicedoc", repositoryId)),
                Title = new TextSyndicationContent("Base Types"),
            };
            entry.AddProperties(properties);

            // Format the syndication item as an Atom entry
            return new Atom10ItemFormatter(entry);
        }

        /// <summary>
        /// Returns the list of all Document Objects in the specified Version Series, sorted by cmis:creationDate descending.
        /// </summary>
        /// <param name="repositoryId">The id of the repository.</param>
        /// <param name="objectId">The id of the object.</param>
        /// <param name="filter">Value indicating which properties for Objects that must be returned. (Optional, default: Not set)</param>
        /// <param name="includeAllowableActions">If true, then the the available actions for each object in the result set must be returned. (Optional, default: false)</param>
        /// <returns>List of document objects, formatted as Atom 1.0 Feed.</returns>
        [WebGet(UriTemplate = "/{repositoryId}/object/{objectId}/allversions")]
        public Atom10FeedFormatter GetAllVersions(string repositoryId, string objectId)
        {
            string filter = RequestParameter.Filter;
            bool includeAllowableActions = RequestParameter.IncludeAllowableActions;

            OutgoingWebResponseContext ctx = WebOperationContext.Current.OutgoingResponse;
            ctx.ContentType = CmisContentType.Feed;

            // Repository ID must be specified
            if (String.IsNullOrEmpty(repositoryId))
            {
                ctx.StatusCode = ServiceException.InvalidArgument;
                ctx.StatusDescription = String.Format("{0}: The parameters repositoryId and folderId must not be null or empty.",
                    ServiceException.InvalidArgument.ToString());
                return null;
            }

            // Store all the document objects in the specified version series in a CmisObject array
            CmisObject[] cmisObjects = VersioningProviderManager.Provider.GetAllVersions(repositoryId, objectId, filter, includeAllowableActions);

            // Create a feed to hold array content
            CmisSyndicationFeed feed = new CmisSyndicationFeed()
            {
                BaseUri = _baseUri,
                Id = String.Format("urn:uuid:{0}-allversions", objectId),
                RepositoryId = repositoryId,
                SelfLinkUri = new Uri(_baseUri, String.Format("{0}/typechildren", repositoryId)),
                ServiceLinkUri = new Uri(_baseUri, String.Format("{0}/servicedoc", repositoryId)),
                Title = new TextSyndicationContent("All Versions"),
            };
            feed.AddEntries(cmisObjects);
            feed.AddNumItems();

            // Format feed as an Atom feed
            return new Atom10FeedFormatter(feed);
        }
    }
}
